package org.apache.flink.connector.httptm;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * <ol>
 * date:2021/4/27 editor:yanghongjian
 * <li>创建文档</li>
 * <li>通用http请求工具类</li>
 * </ol>
 * <ol>
 *
 * @author <a href="mailto:12719889@qq.com">YangHongJian</a>
 * @version 3.0
 * @since 1.8
 */
public final class HttpTools {
    private static final int CONNECT_TIME_OUT = 15000;
    private static final int READ_TIME_OUT = 20000;

    /**
     * Http的请求
     *
     * @param uri       请求的地址
     * @param mapHeader 请求头集合
     * @return 请求返回的字节数据
     */
    public static byte[] getRequest(URI uri, Map<String, String> mapHeader) {
        return httpRequest(uri, "GET", mapHeader, null, "", 0, CONNECT_TIME_OUT, READ_TIME_OUT);
    }

    /**
     * Http的请求
     *
     * @param uri       请求的地址
     * @param mapHeader 请求头集合
     * @param byteBody  请求体的内容
     * @return 请求返回的字节数据
     */
    public static byte[] postRequest(URI uri, Map<String, String> mapHeader, byte[] byteBody) {
        return httpRequest(uri, "POST", mapHeader, byteBody, "", 0, CONNECT_TIME_OUT, CONNECT_TIME_OUT);
    }

    /**
     * Http的请求
     *
     * @param uri       请求的地址
     * @param method    请求方法
     * @param mapHeader 请求头集合
     * @param byteBody  请求体的内容
     * @return 请求返回的字节数据
     */
    public static byte[] httpRequest(URI uri, String method, Map<String, String> mapHeader, byte[] byteBody) {
        return httpRequest(uri, method, mapHeader, byteBody, "", 0, CONNECT_TIME_OUT, CONNECT_TIME_OUT);
    }

    /**
     * Http的请求
     *
     * @param uri            请求的地址
     * @param method         请求方法
     * @param mapHeader      请求头集合
     * @param connectTimeout 连接时间
     * @param readTimeout    读取时间
     * @param byteBody       请求体的内容
     * @return 请求返回的字节数据
     */
    public static byte[] httpRequest(URI uri, String method, Map<String, String> mapHeader, byte[] byteBody,
                                     int connectTimeout, int readTimeout) {
        return httpRequest(uri, method, mapHeader, byteBody, "", 0, connectTimeout, readTimeout);
    }

    /**
     * Http的请求
     *
     * @param uri            请求的地址(支持http/https)
     * @param method         请求方法
     * @param mapHeader      请求头集合
     * @param byteBody       请求体的内容
     * @param proxyHost      代理主机
     * @param proxyPort      代理端口
     * @param connectTimeout 连接时间
     * @param readTimeout    读取时间
     * @return 请求返回的字节数据
     */
    public static byte[] httpRequest(URI uri, String method, Map<String, String> mapHeader, byte[] byteBody,
                                     String proxyHost, final int proxyPort, int connectTimeout, int readTimeout) {

        if ("https".equals(uri.getScheme())) {
            return httpsRequest(uri, method, mapHeader, byteBody, proxyHost, proxyPort, connectTimeout, readTimeout);
        }

        HttpURLConnection urlConnection = null;
        InputStream inputStream;
        try {
            System.out.println("http request url: " + uri);
            URL serverUrl = uri.toURL();
            if (!isEmpty(proxyHost) && proxyPort != 0) {
                Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
                urlConnection = (HttpURLConnection) (serverUrl.openConnection(proxy));
            } else {
                urlConnection = (HttpURLConnection) (serverUrl.openConnection());
            }

            urlConnection.setDoInput(true);

            // 设置不使用缓存， 默认为 true 使用缓存
            urlConnection.setUseCaches(false);
            //注意：此处 方法必须设置为 大写，否则会报如下错误
            //java.net.ProtocolException: Invalid HTTP method: get/post
            urlConnection.setRequestMethod(method.toUpperCase());

            urlConnection.setConnectTimeout(connectTimeout);
            urlConnection.setReadTimeout(readTimeout);
            // 设置单次请求是否支持重定向，默认为 setFollowRedirects 方法设置的值
            urlConnection.setInstanceFollowRedirects(false);
            // 设置是否进行重定向，注意此处为 静态方法，表示所有的请求都不支持重定向，默认为true
            HttpURLConnection.setFollowRedirects(false);

            if (Objects.nonNull(mapHeader) && mapHeader.size() > 0) {
                for (Map.Entry<String, String> entry : mapHeader.entrySet()) {
                    urlConnection.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }

            //封装的请求头 urlConnection.getRequestProperties();
            if (Objects.nonNull(byteBody) && byteBody.length > 0) {
                //需要写数据到请求体中 因为默认是GET请求，所以
                urlConnection.setDoOutput(true);
                OutputStream outputStream = urlConnection.getOutputStream();    // 获取输出流
                outputStream.write(byteBody);
                outputStream.flush();
                outputStream.close();
            }

            // 调用打开连接， 调用此方法，只是建立一个连接，并不会发送数据。
            //urlConnection.connect();
            //getInputStream()会隐式的调用urlConnection.connect() 并将请求体上传至服务器
            inputStream = urlConnection.getInputStream();   // 获取输入流
            if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                //返回的请求头 urlConnection.getHeaderFields();
                try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = br.readLine()) != null) {
                        sb.append(line);
                    }
                    String result = sb.toString();
                    System.out.println("http request url: " + uri + ", result: " + (result.length() > 600 ? result.substring(0, 500) : result));
                    return result.getBytes(StandardCharsets.UTF_8);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
        return null;
    }

    /**
     * Https的请求
     *
     * @param uri            请求的地址
     * @param method         请求方法
     * @param mapHeader      请求头集合
     * @param byteBody       请求体的内容
     * @param proxyHost      代理主机
     * @param proxyPort      代理端口
     * @param connectTimeout 连接时间
     * @param readTimeout    读取时间
     * @return 请求返回的字节数据
     */
    public static byte[] httpsRequest(URI uri, String method, Map<String, String> mapHeader, byte[] byteBody,
                                      String proxyHost, final int proxyPort, int connectTimeout, int readTimeout) {
        HttpsURLConnection urlsConnection = null;
        InputStream inputStream;
        try {
            URL serverUrl = uri.toURL();
            if (!isEmpty(proxyHost) && proxyPort != 0) {
                Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
                urlsConnection = (HttpsURLConnection) (serverUrl.openConnection(proxy));
            } else {
                urlsConnection = (HttpsURLConnection) (serverUrl.openConnection());
            }
            if ("https".equals(uri.getScheme())) {
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, new TrustManager[]{new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }}, new SecureRandom());
//                urlsConnection.setHostnameVerifier(new HostnameVerifier() {
//                    @Override
//                    public boolean verify(String hostname, SSLSession session) {
//                        return true;
//                    }
//                });
                urlsConnection.setHostnameVerifier((hostname, session) -> true);
            }
            urlsConnection.setDoInput(true);
            urlsConnection.setUseCaches(false);
            //注意：此处 方法必须设置为 大写，否则会报如下错误
            //java.net.ProtocolException: Invalid HTTP method: get/post
            urlsConnection.setRequestMethod(method.toUpperCase());
            urlsConnection.setConnectTimeout(connectTimeout);
            urlsConnection.setReadTimeout(readTimeout);

            // 设置单次请求是否支持重定向，默认为 setFollowRedirects 方法设置的值
            urlsConnection.setInstanceFollowRedirects(false);
            // 设置是否进行重定向，注意此处为 静态方法，表示所有的请求都不支持重定向，默认为true
            HttpURLConnection.setFollowRedirects(false);

            if (mapHeader != null && mapHeader.size() > 0) {
                for (Map.Entry<String, String> entry : mapHeader.entrySet()) {
                    urlsConnection.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            //封装的请求头 urlConnection.getRequestProperties();
            if (byteBody != null && byteBody.length > 0) {
                //需要写数据到请求体中 因为默认是GET请求，所以
                urlsConnection.setDoOutput(true);
                OutputStream outputStream = urlsConnection.getOutputStream();
                outputStream.write(byteBody);
                outputStream.flush();
                outputStream.close();
            }

            //getInputStream()会隐式的调用urlConnection.connect() 并将请求体上传至服务器
            inputStream = urlsConnection.getInputStream();
            if (urlsConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                //返回的请求头 urlConnection.getHeaderFields();
                //封装输入流is，并指定字符集
                try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                    StringBuilder sb = new StringBuilder();
                    String line;
                    while ((line = br.readLine()) != null) {
                        sb.append(line);
                    }
                    return sb.toString().getBytes(StandardCharsets.UTF_8);
                }
            }
        } catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            if (urlsConnection != null) {
                urlsConnection.disconnect();
            }
        }
        return null;
    }

    /**
     * 将请求的内容转为 url?a=b&i=j&c=d
     *
     * @param url      请求的前缀
     * @param paramMap 请求的参数
     * @return url?a=b&i=j&c=d
     */
    public static String formatUrlWithParam(String url, Map<String, String> paramMap) {
        StringBuilder urlWithParas = new StringBuilder(isEmpty(url) ? "" : url);
        String paras = joinParam(paramMap);
        if (!isEmpty(paras)) {
            urlWithParas.append("?").append(paras);
        }
        return urlWithParas.toString();
    }

    /**
     * 将请求的内容转为 url?a=b&i=j&c=d
     *
     * @param url      请求的地址
     * @param paramMap 请求的参数
     * @return url?a=b&i=j&c=d
     */
    public static String formatUrlWithParamEncode(String url, Map<String, String> paramMap) {
        StringBuilder urlWithParas = new StringBuilder(isEmpty(url) ? "" : url);
        String paras = joinParaWithEncode(paramMap);
        if (!isEmpty(paras)) {
            urlWithParas.append("?").append(paras);
        }
        return urlWithParas.toString();
    }

    /**
     * 将K-V的参数转为k&v的形式
     *
     * @param paramMap map参数
     * @return 返回k&v字符串
     */
    public static String joinParam(Map<String, String> paramMap) {
        if (paramMap == null || paramMap.size() == 0) {
            return "";
        }
        List<String> listStr = new ArrayList<>();
        paramMap.forEach((key, val) -> {
            listStr.add(String.format("%s=%s", key, val));
        });
        return String.join("&", listStr);
    }

    /**
     * 将K-V的参数转为k&v的形式并进行encode编码
     *
     * @param paramMap map参数
     * @return 返回k&v字符串
     */
    public static String joinParaWithEncode(Map<String, String> paramMap) {
        if (Objects.isNull(paramMap) || paramMap.size() == 0) {
            return "";
        }
        List<String> listStr = new ArrayList<>();
        paramMap.forEach((key, val) -> {
            listStr.add(String.format("%s=%s", key, utf8Encode(val)));
        });
        return String.join("&", listStr);
    }

    /**
     * 是否为空的判断
     *
     * @param str 字符串
     * @return 返回真假
     */
    private static boolean isEmpty(CharSequence str) {
        return (null == str || str.length() == 0);
    }

    /**
     * 将字符串进行UTF-8编码
     *
     * @param str 字符串
     * @return 返回UTF-8编码
     */
    private static String utf8Encode(String str) {
        if (!isEmpty(str) && str.getBytes().length != str.length()) {
            try {
                return URLEncoder.encode(str, StandardCharsets.UTF_8.toString());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return str;
            }
        }
        return str;
    }

}
